﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using J_FoundationClasses.Programacao;
using J_FoundationClasses.Operacao;
using J_FoundationClasses.Teste;


namespace XYSC_1
{
    public class XYSC1_V2 : IProcessador
    {

        // =====================================================
        //
        // Métodos de acesso aos definições que compõem o modelo do processador no J-Operation
        //
        //  ######### methods and readonlies are ugly, better use {get;}
        //
        public readonly Tokens RegsGer;  // Registradores Gerais
        public readonly Tokens RegsEsp;  // Registradores Especiais
        public readonly Tokens Condicoes;
        private List<EspecTeste> _Testes;

        private readonly Tokens _Mnemos;   // Instruções da maquina representados pelo código mnemónico
        public Tokens Mnemos()
        {
            return _Mnemos;
        }

        public List<EspecTeste> TestesEspecificos
        {
            get {return _Testes;}
        }

        // ===============================
        //
        // Constructor e métodos auxiliares
        //
        public XYSC1_V2()
        {
            RegsGer = IniRG();
            RegsEsp = IniRE();
            _Mnemos = IniMN();
            Condicoes = IniCC();
            _Testes = IniTestes();
        }
        private Tokens IniRG()
        {
            // inicializar os registradores gerais
            Tokens tt = new Tokens();

            tt.Itens.Add(new TokenUint("r0", 0));
            tt.Itens.Add(new TokenUint("r1", 1));
            tt.Itens.Add(new TokenUint("r2", 2));
            tt.Itens.Add(new TokenUint("r3", 3));
            tt.Itens.Add(new TokenUint("r4", 4));
            tt.Itens.Add(new TokenUint("r5", 5));
            tt.Itens.Add(new TokenUint("r6", 6));
            tt.Itens.Add(new TokenUint("r7", 7));
            tt.Itens.Add(new TokenUint("r8", 8));
            tt.Itens.Add(new TokenUint("r9", 9));
            tt.Itens.Add(new TokenUint("rA", 10));
            tt.Itens.Add(new TokenUint("rB", 11));
            tt.Itens.Add(new TokenUint("rC", 12));
            tt.Itens.Add(new TokenUint("rD", 13));
            tt.Itens.Add(new TokenUint("rE", 14));
            tt.Itens.Add(new TokenUint("rF", 15));
            return tt;

        }
        private Tokens IniRE()
        {
            return null;
        }
        private Tokens IniMN()
        {
            Tokens tt = new Tokens();

            tt.Itens.Add(new Mnemo("LD", 0x01, MH_op, MV_mais4, FV_op)); //####
            tt.Itens.Add(new Mnemo("ST", 0x02, MH_op, MV_mais4, FV_op)); //####

            tt.Itens.Add(new Mnemo("PUSH", 0x71, MH_op_re, MV_mais4, FV_op_re)); 
            tt.Itens.Add(new Mnemo("POP", 0x72, MH_op_re, MV_mais4, FV_op_re));

            tt.Itens.Add(new Mnemo("MOV", 0x21, MH_op, MV_mais4, FV_op_re_re)); 
            tt.Itens.Add(new Mnemo("MVI", 0x22, MH_op, MV_mais4, FV_op_im));

            tt.Itens.Add(new Mnemo("ADD", 0x31, MH_op_re_re, MV_mais4, FV_op_re_re));
            tt.Itens.Add(new Mnemo("SUB", 0x32, MH_op_re_re, MV_mais4, FV_op_re_re));
            tt.Itens.Add(new Mnemo("AND", 0x41, MH_op_re_re, MV_mais4, FV_op_re_re));
            tt.Itens.Add(new Mnemo("OR", 0x42, MH_op_re_re, MV_mais4, FV_op_re_re));
            tt.Itens.Add(new Mnemo("XOR", 0x43, MH_op_re_re, MV_mais4, FV_op_re_re));
            tt.Itens.Add(new Mnemo("NOT", 0x61, MH_op, MV_mais4, FV_op)); //####
            tt.Itens.Add(new Mnemo("SBIT", 0x61, MH_op, MV_mais4, FV_op)); //####
            tt.Itens.Add(new Mnemo("SHL", 0x51, MH_op, MV_mais4, FV_op)); //####
            tt.Itens.Add(new Mnemo("ROL", 0x52, MH_op, MV_mais4, FV_op)); //####

            tt.Itens.Add(new Mnemo("NOP", 0x61, MH_op, MV_mais4, FV_op));
            tt.Itens.Add(new Mnemo("JMP", 0x62, MH_op_edc, MV_mais4, FV_op_im));
            tt.Itens.Add(new Mnemo("JCOND", 0x63, MH_op_cond_edc, MV_mais4, FV_op_cond_im)); 
            tt.Itens.Add(new Mnemo("CALL", 0x64, MH_op_edc, MV_mais4, FV_op));
            tt.Itens.Add(new Mnemo("RET", 0x65, MH_op, MV_mais4, FV_op));
            tt.Itens.Add(new Mnemo("HALT", 0x66, MH_op, MV_mais4, FV_op));

            return tt;
        }
        private Tokens IniCC()
        {
            // inicializar os códigos de condição
            // os bits de números não tem significado individual
            Tokens tt = new Tokens();

            tt.Itens.Add(new TokenUint("EQ", 2));
            tt.Itens.Add(new TokenUint("NE", 3));
            tt.Itens.Add(new TokenUint("LT", 4));
            tt.Itens.Add(new TokenUint("LE", 5));
            tt.Itens.Add(new TokenUint("GT", 6));
            tt.Itens.Add(new TokenUint("GE", 7));
            tt.Itens.Add(new TokenUint("OV", 8));
            tt.Itens.Add(new TokenUint("NO", 9));
            tt.Itens.Add(new TokenUint("CY", 0xA));
            tt.Itens.Add(new TokenUint("NC", 0xB));

            return tt;
        }
        private List<EspecTeste> IniTestes()
        {
            // como testar XYSC_1? É uma longa história...
            List<EspecTeste> Testes = new List<EspecTeste>();

            EspecTesteInter et;
            EspecReacao val;
            // ######### OK e ERR deveriam ser membros estáticos
            EspecReacao ok = new EspecReacao(false, "OK\r\n");
            EspecReacao err = new EspecReacao(false, "ERR\r\n");
            EspecInter ei;


            // Reg0 <- 12 34 56 78, verificar
            et = new EspecTesteInter("TesteInteracao", "Acesso Reg0");

            // ###################################################################
            // precisa mais constructors para
            // Interacoes.Add(new EspecInter(">00 11..", ok, err)
            // Interacoes.Add(new EspecInter("<00", "= 12 ..", err)
            ei = new EspecInter();
            ei.TelegramaComando = System.Text.Encoding.ASCII.GetBytes(">00 12 34 56 78\r\n");
            ei.Respostas = new EspecReacao[2] { ok, err };
            ei.ConsidChar0 = true;
            ei.ConsidIndErro = true;
            et.Interacoes.Add(ei);

            ei = new EspecInter();
            ei.TelegramaComando = System.Text.Encoding.ASCII.GetBytes("<00\r\n");
            val = new EspecReacao(false, "= 12 34 56 78\r\n");
            ei.Respostas = new EspecReacao[2] { val, err };
            ei.ConsidChar0 = true;
            ei.ConsidIndErro = true;
            et.Interacoes.Add(ei);

            Testes.Add(et);

            return Testes;
        }

        // =========================================
        //
        // atributos específos do processador
        //
        public byte[] Identificacao()
        {
            byte[] ID = new byte[8];
            ID[0] = (byte)'X';
            ID[1] = (byte)'Y';
            ID[2] = (byte)'S';
            ID[3] = (byte)'C';
            ID[4] = (byte)'1';
            ID[5] = (byte)'_';
            ID[6] = (byte)'V';
            ID[7] = (byte)'2';
            return ID;
        }
        public UInt64 EndMax()
        {
            return (1L << 16) - 1;  // memória de 2^16 bytes
        }
        public Type TipoInstrucao()
        {
            return typeof(IM_XYSC1_V2);
        }



        // métodos para produzir representação binária
        //
        //
        void MH_op(ref Instrucao inst)
        {
            // somente opcode
            byte[] ba = new byte[4];
            uint OpCode = (_Mnemos.Item(inst.Operacao) as Mnemo).OpCode;
            ba[3] = Convert.ToByte(OpCode);
            inst.RepBin = ba;
        }
        void MH_op_edc(ref Instrucao inst)
        {
            // opcode mais um endereço

            IM_XYSC1_V2 im = (inst as IM_XYSC1_V2);
            byte[] ba = new byte[4];
            ushort edc = 0;
            FixupFonte xx = new FixupFonte(2);  // relocação deve tratar 2 bytes, ...
            xx.Distanca = 0; // ... começando no byte 0

            // tratar opcode
            uint OpCode = (_Mnemos.Item(im.Operacao) as Mnemo).OpCode;
            ba[3] = Convert.ToByte(OpCode);

            // tratar atributo endereço
            Valor vv = new Valor(inst.Imediato);
            switch (vv.Tipo)
            {
                case TipoVal.Vazio:
                    throw new ArgumentException("Instrução precisa um endereço.");
                case TipoVal.Ilegal:
                    throw new ArgumentException(vv.Mensagem);
                case TipoVal.Ident:
                    // encher fixup
                    xx.Simbolico = true;
                    xx.Rotulo = vv.Lexema;
                    break;
                case TipoVal.NumInt:
                    if (vv.ValNum < 0) throw new ArgumentException("Endereço não deve ser negativo.");
                    if (vv.ValNum > ushort.MaxValue) throw new ArgumentException("Endereço não deve ser acima de 64K.");
                    edc = (ushort)vv.ValNum;

                    // montar o endereço
                    byte[] eBytes = BitConverter.GetBytes(edc);
                    ba[1] = eBytes[1];
                    ba[0] = eBytes[0];
                    break;
                default:
                    throw new ArgumentException("Atributo não é um endereço");
            }

            inst.RepBin = ba;
            inst.Fixup = xx;
        }
        void MH_op_cond_edc(ref Instrucao inst)
        {
            // opcode mais um endereço

            IM_XYSC1_V2 im = (inst as IM_XYSC1_V2);
            byte[] ba = new byte[4];
            ushort edc = 0;
            FixupFonte xx = new FixupFonte(2);  // relocação deve tratar 2 bytes, ...
            xx.Distanca = 0; // ... começando no byte 0

            // tratar opcode
            uint OpCode = (_Mnemos.Item(im.Operacao) as Mnemo).OpCode;
            ba[3] = Convert.ToByte(OpCode);

            // tratar código de condição
            uint CC = (Condicoes.Item(im.Condicao) as TokenUint).Valor();
            ba[2] = (byte)CC;

            // tratar atributo endereço
            Valor vv = new Valor(inst.Imediato);
            switch (vv.Tipo)
            {
                case TipoVal.Vazio:
                    throw new ArgumentException("Instrução precisa um endereço.");
                case TipoVal.Ilegal:
                    throw new ArgumentException(vv.Mensagem);
                case TipoVal.Ident:
                    // encher fixup
                    xx.Simbolico = true;
                    xx.Rotulo = vv.Lexema;
                    break;
                case TipoVal.NumInt:
                    if (vv.ValNum < 0) throw new ArgumentException("Endereço não deve ser negativo.");
                    if (vv.ValNum > ushort.MaxValue) throw new ArgumentException("Endereço não deve ser acima de 64K.");
                    edc = (ushort)vv.ValNum;

                    // montar o endereço
                    byte[] eBytes = BitConverter.GetBytes(edc);
                    ba[1] = eBytes[1];
                    ba[0] = eBytes[0];
                    break;
                default:
                    throw new ArgumentException("Atributo não é um endereço");
            }

            inst.RepBin = ba;
            inst.Fixup = xx;
        }


        void MH_op_re(ref Instrucao inst)
        {
            IM_XYSC1_V2 im = (inst as IM_XYSC1_V2);
            byte[] ba = new byte[4];

            uint OpCode = (_Mnemos.Item(im.Operacao) as Mnemo).OpCode;
            uint RD = (RegsGer.Item(im.RegDest) as TokenUint).Valor();

            if (RD > 15) throw new ArgumentException("Número de registrador > 15 ilegítimo");

            ba[3] = Convert.ToByte(OpCode);
            ba[2] = Convert.ToByte(RD);
            ba[2] *= 16;  // 4 bits à esquerda

            inst.RepBin = ba;
        }
        void MH_op_re_re(ref Instrucao inst)
        {
            IM_XYSC1_V2 im = (inst as IM_XYSC1_V2);
            byte[] ba = new byte[4];

            uint OpCode = (_Mnemos.Item(im.Operacao) as Mnemo).OpCode;
            uint RD = (RegsGer.Item(im.RegDest) as TokenUint).Valor();
            uint RF = (RegsGer.Item(im.RegFont) as TokenUint).Valor();

            if (RD > 15) throw new ArgumentException("Número de registrador > 15 ilegítimo");
            if (RF > 15) throw new ArgumentException("Número de registrador > 15 ilegítimo");

            ba[3] = Convert.ToByte(OpCode);
            ba[2] = Convert.ToByte(RD);
            ba[2] *= 16;  // 4 bits à esquerda
            ba[2] += Convert.ToByte(RF);

            inst.RepBin = ba;
        }

        // métodos para derivar o próximo endereço
        //
        //
        UInt64 MV_mais4(UInt64 Inicio, ref Instrucao Inst)
        {
            // Montar verticalmente para instruções. Tamanho = 4 bytes, alinhamento = *4

            // Alinhamento ao multiplo de 4. Pode causar lacunha no objeto binário
            UInt64 corr = Inicio % 4;
            if (corr != 0) Inst.edcAtual = Inicio + (4 - corr);
            else Inst.edcAtual = Inicio;

            // Determinar próximo endereço livre na memória
            Inst.edcProx = Inst.edcAtual + 4;
            return  Inst.edcProx;
        }


        // Formatos para representação visual
        //
        //
        #region
        string FV_op = "{0} {1} {6}";
        string FV_op_im = "{0} {1} {2}, {5} {6}";
        string FV_op_re = "{0} {1} {2} {6}";
        string FV_op_cond_im = "{0} {1} {4} {5} {6}";
        string FV_op_re_re = "{0} {1} {2}, {3} {6}";
        #endregion
    }
}
